#include <iostream>
#include <memory>

//Abstract productA
class AbstractProductA{
public:
    virtual ~AbstractProductA() = default;
    virtual std::string UsefulFunctionA() const = 0;
};

//Concrete productA1
class ConcreteProductA1 : public AbstractProductA{
public:
    std::string UsefulFunctionA() const override{
        return "The result of the product A1.";
    }
};

//Concrete productA2
class ConcreteProductA2 : public AbstractProductA{
public:
    std::string UsefulFunctionA() const override{
        return "The result of the product A2.";
    }
};

//Abstract productB
class AbstractProductB{
public:
    virtual ~AbstractProductB() = default;
    virtual std::string UsefulFunctionB() const = 0;
    virtual std::string AnotherUsefulFunctionB(const AbstractProductA& collaborator) const = 0;
};

//Concrete productB1
class ConcreteProductB1 : public AbstractProductB{
public:
    std::string UsefulFunctionB() const override{
        return "The result of the product B1";
    }

    std::string AnotherUsefulFunctionB(const AbstractProductA& collaborator) const override{
        const std::string result = collaborator.UsefulFunctionA();
        return "The result of the B1 collaborating with (" + result + ")";
    }
};

//Concrete productB2
class ConcreteProductB2 : public AbstractProductB{
public:
    std::string UsefulFunctionB() const override{
        return "The result of the product B2";
    }

    std::string AnotherUsefulFunctionB(const AbstractProductA& collaborator) const override{
        const std::string result = collaborator.UsefulFunctionA();
        return "The result of the B2 collaborating with (" + result + ")";
    }
};

//Abstract factory
class AbstractFactory{
public:
    virtual ~AbstractFactory() = default;
    virtual std::unique_ptr<AbstractProductA> CreateProductA() const = 0;
    virtual std::unique_ptr<AbstractProductB> CreateProductB() const = 0;
};

//Concrete factory1
class ConcreteFactory1 : public AbstractFactory{
public:
    virtual std::unique_ptr<AbstractProductA> CreateProductA() const override{
        return std::make_unique<ConcreteProductA1>();
    }
    virtual std::unique_ptr<AbstractProductB> CreateProductB() const override{
        return std::make_unique<ConcreteProductB1>();
    }
};

//Concrete factory2
class ConcreteFactory2 : public AbstractFactory{
public:
    virtual std::unique_ptr<AbstractProductA> CreateProductA() const override{
        return std::make_unique<ConcreteProductA2>();
    }
    virtual std::unique_ptr<AbstractProductB> CreateProductB() const override{
        return std::make_unique<ConcreteProductB2>();
    }
};

//Client
void ClientCode(const AbstractFactory& factory){
    const std::unique_ptr<AbstractProductA> productA = factory.CreateProductA();
    const std::unique_ptr<AbstractProductB> productB = factory.CreateProductB();

    std::cout << productB->UsefulFunctionB() << "\n";
    std::cout << productB->AnotherUsefulFunctionB(*productA) << "\n";
}

int main()
{
    std::cout << "Client: Testing client code with the first factory type:\n";
    std::unique_ptr<ConcreteFactory1> factory1 = std::make_unique<ConcreteFactory1>();
    ClientCode(*factory1);

    std::cout << "\nClient: Testing the same client code with the second factory type:\n";
    std::unique_ptr<ConcreteFactory2> factory2 = std::make_unique<ConcreteFactory2>();
    ClientCode(*factory2);

    return 0;
}